using System;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;

namespace iPodShuffle
{
	public class ShuffleReader
	{
		protected Stream m_Stream;

		public ShuffleReader(Stream stream)
		{
			if (null == stream) throw new ArgumentException( "stream" );
			m_Stream = stream;
		}

		public string[] ReadFiles()
		{
			int count;
			ReadHeader( out count );

			string[] files = new string[count];

			for (int i=0; i<count; i++)
				files[i] = ReadFile();

			return files;
		}

		byte[] _buff3 = new byte[3];
		int ReadUInt24()
		{
			if ( 3 != m_Stream.Read( _buff3, 0, 3 ) )
				throw new EndOfStreamException();

			int i=(_buff3[0] << 16)
				| (_buff3[1] <<  8)
				|  _buff3[2];

			return i;
		}

		void ReadHeader( out int fileCount )
		{
			int headerSize;
			int unknown;

			fileCount  = ReadUInt24();
			unknown    = ReadUInt24();
			headerSize = ReadUInt24();
			unknown    = ReadUInt24();
			unknown    = ReadUInt24();
			unknown    = ReadUInt24();
		}

		string ReadFile()
		{
			int startTime;
			int stopTime;
			int volume;
			int fileType;
			string file = null;
			bool shuffleFlag;
			bool bookmarkFlag;

			ReadItem( out startTime, out stopTime, out volume, out fileType, out file, out shuffleFlag, out bookmarkFlag );

			return file;
		}

		void ReadItem( out int startTime, out int stopTime, out int volume, out int fileType, out string file, out bool shuffleFlag, out bool bookmarkFlag )
		{
			int structSize;
			int unknown;
			const int FILENAMELEN = 522; // in bytes
			int bufSize;

			structSize = ReadUInt24();
			unknown	   = ReadUInt24();
			startTime  = ReadUInt24();
			unknown    = ReadUInt24();
			unknown    = ReadUInt24();
			stopTime   = ReadUInt24();
			unknown    = ReadUInt24();
			unknown    = ReadUInt24();
			volume	   = ReadUInt24();
			fileType   = ReadUInt24();
			bufSize    = ReadUInt24();

			byte[] buf = new byte[FILENAMELEN];
			if ( buf.Length != m_Stream.Read( buf, 0, buf.Length ) )
				throw new EndOfStreamException();

			file = Encoding.Unicode.GetString( buf );
			file = file.TrimEnd('\0');

			// defaults - the last item could be incomplete
			shuffleFlag  = true;
			bookmarkFlag = false;
			try
			{
				if ( 3 != m_Stream.Read( buf, 0, 3 ) )
					throw new EndOfStreamException();

				shuffleFlag  = 0 != buf[0];
				bookmarkFlag = 0 != buf[1];
				unknown		 =      buf[2];
			}
			catch 
			{}
		}
	}

	public class ShuffleWriter
	{
		protected Stream m_Stream;

		public ShuffleWriter( Stream stream )
		{
			if (null == stream) throw new ArgumentException( "stream" );
			m_Stream = stream;
		}

		public void Write( string[] files )
		{
			WriteHeader( files.Length );

			foreach ( string file in files )
				WriteItem( file );
		}

		byte[] _buff3 = new byte[3];
		void WriteUInt24( int value )
		{
			if ( 0 != (value >> 24) )
				throw new ArgumentOutOfRangeException();
			
			_buff3[0] = (byte)((value >> 16) & 0xFF);
			_buff3[1] = (byte)((value >>  8) & 0xFF);
			_buff3[2] = (byte)( value        & 0xFF);

			m_Stream.Write( _buff3, 0, 3 );
		}

		void WriteHeader( int fileCount )
		{
			const int headerSize = 0x000012; // 18 bytes = 6 x UInt24
			const int unknown    = 0;

			WriteUInt24( fileCount  );
			WriteUInt24( 0x010800   );
			WriteUInt24( headerSize ); // 0x000012;
			WriteUInt24( unknown    );
			WriteUInt24( unknown    );
			WriteUInt24( unknown    );
		}

		void WriteItem( string file )
		{
			byte fileType;
			string ext = Path.GetExtension(file).ToLower();
			switch ( ext )
			{
				case ".mp3": fileType = 1; break;
				case ".m4a":
				case ".m4b":
				case ".m4p": fileType = 2; break;
				case ".aa" : fileType = 4; break;
				default    : fileType = 0; break;
			}

			bool bookmarkFlag = ext == "m4b" || ext == "aa";

			const int startTime		= 0;
			const int stopTime		= 0;
			const int volume		= 0; // 0x64;
			const bool shuffleFlag	= true;

			WriteItem( startTime, stopTime, volume, fileType, file, shuffleFlag, bookmarkFlag );
		}

		void WriteItem( int startTime, int stopTime, int volume, int fileType, string file, bool shuffleFlag, bool bookmarkFlag )
		{
			const int structSize  = 0x00022E; // 558
			const int unknown     = 0;
			const int FILENAMELEN = 522; // in bytes
			const int bufSize	  = 0x200; // 512
			const byte byte1	  = 1;
			const byte byte0	  = 0;

			if ( 1<file.Length && ':'==file[1] ) 
				file = file.Remove(0,2);

			file = file.Replace( '\\', '/');

			WriteUInt24( structSize );
			WriteUInt24( 0x5aa501   ); 
			WriteUInt24( startTime  );
			WriteUInt24( unknown    );
			WriteUInt24( unknown    );
			WriteUInt24( stopTime   );
			WriteUInt24( unknown    );
			WriteUInt24( unknown	);
			WriteUInt24( volume		); // 0x64;
			WriteUInt24( fileType   );
			WriteUInt24( bufSize	); // ?? filename buff len ??
	
			byte[] buf = new byte[FILENAMELEN];
			Encoding.Unicode.GetBytes( file, 0, file.Length, buf, 0 );
			m_Stream.Write( buf, 0, buf.Length );

			buf[0] =  shuffleFlag? byte1: byte0;
			buf[1] = bookmarkFlag? byte1: byte0;
			buf[2] = unknown;
			m_Stream.Write( buf, 0, 3 );
		}
	}
}
